﻿using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using DBBuilder.DBDataStructure;
using System.Reflection;
using AppsGenerator.Models;
using AppsGenerator.Utils;
using System;

namespace AppsGenerator.Generators
{
    /// <summary>
    /// Defines inheritance fields and methods for generators 
    /// </summary>
    public class BaseGenerator
    {
        #region Constants & Fields

        public static class BaseConsts
        {
            /// <summary>
            /// Add 1 tab string
            /// </summary>
            public const string TAB1 = "\t";

            /// <summary>
            /// Add 2 tabs string
            /// </summary>
            public const string TAB2 = "\t\t";

            /// <summary>
            /// Add 3 tabs string
            /// </summary>
            public const string TAB3 = "\t\t\t";

            /// <summary>
            /// Add 4 tabs string
            /// </summary>
            public const string TAB4 = "\t\t\t\t";

            /// <summary>
            /// Add 5 tabs string
            /// </summary>
            public const string TAB5 = "\t\t\t\t\t";

            /// <summary>
            /// Add 6 tabs string
            /// </summary>
            public const string TAB6 = "\t\t\t\t\t\t";

            /// <summary>
            /// Add 7 tabs string
            /// </summary>
            public const string TAB7 = "\t\t\t\t\t\t\t";

            /// <summary>
            /// Add 8 tabs string
            /// </summary>
            public const string TAB8 = "\t\t\t\t\t\t\t\t";

            /// <summary>
            /// Add 9 tabs string
            /// </summary>
            public const string TAB9 = "\t\t\t\t\t\t\t\t\t";

            /// <summary>
            /// Maximum bytes for a StringBuilder
            /// </summary>
            public const int MAX_BYTES = 5120;

            /// <summary>
            /// The tables will be skipped to creat controller
            /// </summary>
            public static readonly string[] SkipTables = { "aspnet_Users", "__MigrationHistory" };

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstGlobalNameSpace = "[GlobalNameSpace]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstTable = "[Table]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstPluralizeTable = "[PluralizeTable]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstForeignTable = "[ForeignTable]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstPluralizeForeignTable = "[PluralizeForeignTable]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstForeignTableModel = "[ForeignTableModel]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstArgs = "[Args]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstPrimaryKey = "[PrimaryKey]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstForeignKey = "[ForeignKey]";

            /// <summary>
            /// This value will be replaced at run time
            /// </summary>
            public const string ConstImage = "[Image]";
        }
        

        /// <summary>
        /// Repository template file
        /// </summary>
        public string XMLTemplate = string.Empty;

        #endregion

        #region Properties

        /// <summary>
        /// Global namespace for all generated projects, this value will be updated from configuration file
        /// </summary>
        public string GlobalNameSpace
        {
            get
            {
                return WebConfiguration.Instance.NameSpace;
            }
        }

        /// <summary>
        /// Path is corresponding with directory which contains generated files, 
        /// this value will be updated at runtime
        /// </summary>
        public string DirPath { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// Get application path
        /// </summary>
        /// <returns></returns>
        public string GetAppPath()
        {
            return Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName;
        }

        /// <summary>
        /// Initialize directory
        /// if folder is existent, remove old files in it
        /// Else create new folder
        /// </summary>
        /// <param name="dirPath">Directory for initializing</param>
        /// <param name="removeOldFiles"> </param>
        public virtual void InitializeDir(string dirPath, bool removeOldFiles = true)
        {
            // Remove old files have been created before
            if (Directory.Exists(dirPath))
            {
                if (removeOldFiles)
                {
                    this.DeleteOldFiles(dirPath);   
                }
            }
            else // Create folder Controllers if it does not exist 
            {
                Directory.CreateDirectory(dirPath);
            }
        }

        /// <summary>
        /// Save generated contents to a file
        /// </summary>
        /// <param name="contents">The contents will be saved in a file</param>
        /// <param name="fileName">The file will be saved contents</param>
        public void SaveFile(string contents, string fileName)
        {
            try
            {
                var path = this.GetFilePath(fileName);
                using (StreamWriter outfile = new StreamWriter(path))
                {
                    outfile.Write(contents);
                }
            }
            catch (Exception ex)
            {
                var errMsg = string.Format("SaveFile {0} errors: {1}", fileName, ex.Message);
                throw new IOException(errMsg);
            }
        }

        /// <summary>
        /// Build arguments for a method
        /// </summary>
        /// <param name="attr">The Attribute of a Table</param>
        /// <returns></returns>
        public string BuildArgs(DBBuilder.DBDataStructure.Attribute attr)
        {
            var contents = new StringBuilder(BaseConsts.MAX_BYTES);
            contents.Append(string.Format("{0} {1}", attr.Type.ToStringValue(), attr.Name));
            return contents.ToString();
        }

        /// <summary>
        /// Build arguments for a method
        /// </summary>
        /// <param name="attrs">The Attribute list of a Table</param>
        /// <returns></returns>
        public string BuildArgs(List<DBBuilder.DBDataStructure.Attribute> attrs)
        {
            var contents = new StringBuilder(BaseConsts.MAX_BYTES);

            for (int i = 0; i < attrs.Count; i++)
            {
                contents.Append(i < attrs.Count - 1
                                    ? string.Format("{0} {1}, ", attrs[i].Type.ToStringValue(), attrs[i].Name)
                                    : string.Format("{0} {1}", attrs[i].Type.ToStringValue(), attrs[i].Name));
            }

            return contents.ToString();
        }

        /// <summary>
        /// Checking if item is existent in array items
        /// </summary>
        /// <param name="arrItems">Array items</param>
        /// <param name="item">Item will be checked</param>
        /// <returns>True: existent - False: not existent</returns>
        public bool IsItemExistent(string[] arrItems, string item)
        {
            return arrItems.Any(n => string.Compare(n, item, StringComparison.OrdinalIgnoreCase) == 0);
        }

        /// <summary>
        /// Build a conversion data type of a parameter
        /// </summary>
        /// <param name="attr">The Attribute of a Table</param>
        /// <returns></returns>
        public string BuildParsingData(DBBuilder.DBDataStructure.Attribute attr)
        {
            var contents = new StringBuilder(BaseConsts.MAX_BYTES);

            if (attr.Type == DataType.STRING)
            {
                contents.Append(attr.Name.ToLowerFirstCharacter());
            }
            else
            {
                contents.Append(attr.Type == DataType.DATETIME
                                    ? string.Format("{0} == string.Empty ? DateTime.Now : {1}.Parse({2})",
                                                    attr.Name.ToLowerFirstCharacter(), attr.Type.ToStringValue(),
                                                    attr.Name.ToLowerFirstCharacter())
                                    : string.Format("{0}.Parse({1})", attr.Type.ToStringValue(),
                                                    attr.Name.ToLowerFirstCharacter()));
            }

            return contents.ToString();
        }

        /// <summary>
        /// Build a conversion data type of a parameter
        /// </summary>
        /// <param name="attr">The Attribute of a Table</param>
        /// <returns></returns>
        public string BuildParsingDataAndConvertToLower(DBBuilder.DBDataStructure.Attribute attr)
        {
            var contents = new StringBuilder(BaseConsts.MAX_BYTES);

            if (attr.Type == DataType.STRING)
            {
                contents.Append(attr.Name.ToLower());
            }
            else
            {
                contents.Append(attr.Type == DataType.DATETIME
                                    ? string.Format("{0} == string.Empty ? DateTime.Now : {1}.Parse({2})",
                                                    attr.Name.ToLower(), attr.Type.ToStringValue(), attr.Name.ToLower())
                                    : string.Format("{0}.Parse({1})", attr.Type.ToStringValue(), attr.Name.ToLower()));
            }

            return contents.ToString();
        }

        /// <summary>
        /// Build a conversion data type of a parameter without lower case
        /// </summary>
        /// <param name="attr">The Attribute of a Table</param>
        /// <returns></returns>
        public string BuildParsingDataWithoutLower(DBBuilder.DBDataStructure.Attribute attr)
        {
            var contents = new StringBuilder(BaseConsts.MAX_BYTES);

            if (attr.Type == DataType.STRING)
            {
                contents.Append(attr.Name.ToLower());
            }
            else
            {
                contents.Append(attr.Type == DataType.DATETIME
                                    ? string.Format("{0} == string.Empty ? DateTime.Now : {1}.Parse({2})",
                                                    attr.Name.ToLower(), attr.Type.ToStringValue(), attr.Name.ToLower())
                                    : string.Format("{0}.Parse({1})", attr.Type.ToStringValue(), attr.Name));
            }

            return contents.ToString();
        }

        /// <summary>
        /// Build a conversion data type of a parameter with first lower character
        /// </summary>
        /// <param name="attr">The Attribute of a Table</param>
        /// <returns></returns>
        public string BuildParsingDataWithFirstLowerCharacter(DBBuilder.DBDataStructure.Attribute attr)
        {
            var contents = new StringBuilder(BaseConsts.MAX_BYTES);

            if (attr.Type == DataType.STRING)
            {
                contents.Append(attr.Name.ToLowerFirstCharacter());
            }
            else
            {
                contents.Append(attr.Type == DataType.DATETIME
                                    ? string.Format("{0} == string.Empty ? DateTime.Now : {1}.Parse({2})",
                                                    attr.Name.ToLowerFirstCharacter(), attr.Type.ToStringValue(), attr.Name.ToLowerFirstCharacter())
                                    : string.Format("{0}.Parse({1})", attr.Type.ToStringValue(), attr.Name.ToLowerFirstCharacter()));
            }

            return contents.ToString();
        }

        /// <summary>
        /// Build foreign view models
        /// </summary>
        /// <param name="tbl">Table which contains foreign keys</param>
        /// <returns></returns>
        public string BuildForeignViewModels(Table tbl)
        {
            var result = string.Empty;

            if (!tbl.IsUserTable())
            {
                var contents = new StringBuilder(BaseConsts.MAX_BYTES);
                string MainFK;

                if (tbl.IsTableWithMainForeignKey(out MainFK))
                {
                    var FK =
                        tbl.Attributes.FirstOrDefault(n => n.Name.Equals(MainFK, StringComparison.OrdinalIgnoreCase));
                    if (FK != null)
                    {
                        contents.Append(string.Format("{0} = new List<{1}> {{ this.Rep{2}.SelectByID({3}) }},",
                                                      FK.ReferTo.Pluralize(), FK.ReferTo, FK.ReferTo, FK.Name));
                        result = contents.ToString();
                    }
                }
            }

            return result;
        }

        /// <summary>
        /// Build main foreign key arguments for a method
        /// </summary>
        /// <param name="tbl">Table which contains foreign keys</param>
        /// <returns></returns>
        public string BuildMainForeignKeyArgs(Table tbl)
        {
            var result = string.Empty;
            string MainFK;
            
            if (tbl.IsTableWithMainForeignKey(out MainFK))
            {
                var contents = new StringBuilder(BaseConsts.MAX_BYTES);
                
                var FK = tbl.Attributes.FirstOrDefault(n => n.Name.Equals(MainFK, StringComparison.OrdinalIgnoreCase));
                if (FK != null)
                {
                    contents.Append(string.Format("{0} {1},", FK.Type.ToStringValue(), FK.Name));
                }
                result = contents.ToString();
            }

            return result;
        }

        /// <summary>
        /// Build main foreign key arguments for a method
        /// </summary>
        /// <param name="tbl">Table which contains foreign keys</param>
        /// <returns></returns>
        public string BuildMainForeignKeyArgsWithoutCommas(Table tbl)
        {
            var contents = new StringBuilder(BaseConsts.MAX_BYTES);
            string MainFK;

            if (tbl.IsTableWithMainForeignKey(out MainFK))
            {
                var FK = tbl.Attributes.FirstOrDefault(n => n.Name.Equals(MainFK, StringComparison.OrdinalIgnoreCase));
                if (FK != null)
                {
                    contents.Append(string.Format("{0} {1}", FK.Type.ToStringValue(), FK.Name));
                }
            }

            return contents.ToString();
        }

        /// <summary>
        /// Build main foreign key parsing
        /// </summary>
        /// <param name="tbl">Table which contains foreign keys</param>
        /// <returns></returns>
        public string BuildMainForeignKeyParsing(Table tbl)
        {
            var contents = new StringBuilder(BaseConsts.MAX_BYTES);
            string MainFK;

            if (tbl.IsTableWithMainForeignKey(out MainFK))
            {
                var FK = tbl.Attributes.FirstOrDefault(n => n.Name.Equals(MainFK, StringComparison.OrdinalIgnoreCase));
                if (FK != null)
                {
                    contents.Append(FK.Type == DataType.STRING
                                        ? string.Format("var {0} = Forms.Get(\"{1}\");",
                                                        FK.Type.ToStringValue(), FK.Name)
                                        : string.Format("var {0} = {1}.Parse(Forms.Get(\"{2}\"));", FK.Name,
                                                        FK.Type.ToStringValue(), FK.Name));
                }
            }

            return contents.ToString();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Get file path
        /// </summary>
        /// <param name="filePath">Name of file</param>
        /// <returns></returns>
        private string GetFilePath(string filePath)
        {
            var pathStr = filePath;
            if ((!Path.IsPathRooted(pathStr)))
            {
                pathStr = Path.Combine(this.DirPath, filePath);
            }
            return Path.GetFullPath(pathStr);
        }

        /// <summary>
        /// Deleting all files stored in directory
        /// </summary>
        private void DeleteOldFiles(string dirPath)
        {
            if (Directory.Exists(dirPath))
            {
                try
                {
                    var filePaths = Directory.GetFiles(dirPath);
                    foreach (string filePath in filePaths)
                    {
                        File.Delete(filePath);
                    }
                }
                catch (IOException ex)
                {
                    var errMsg = string.Format("DeleteOldFiles errors: {0}", ex.Message);
                    throw new IOException(errMsg);
                }
            }
        }

        #endregion
    }
}
